راهنمای جامع TypeScript Compiler API، شامل درختهای نحو انتزاعی (AST)، تحلیل، تبدیل و تولید کد برای توسعهدهندگان بینالمللی.
TypeScript Compiler API: تسلط بر دستکاری AST و تبدیل کد
TypeScript Compiler API یک رابط قدرتمند برای تحلیل، دستکاری و تولید کد TypeScript و JavaScript فراهم میکند. در قلب این API، درخت نحو انتزاعی (AST) قرار دارد که یک نمایش ساختاریافته از کد منبع شماست. درک نحوه کار با AST، قابلیتهایی برای ساخت ابزارهای پیشرفته مانند لینترها، فرمتدهندههای کد، تحلیلگران استاتیک و تولیدکنندگان کد سفارشی را باز میکند.
TypeScript Compiler API چیست؟
TypeScript Compiler API مجموعهای از اینترفیسها و توابع TypeScript است که عملکرد داخلی کامپایلر TypeScript را در اختیار قرار میدهد. این API به توسعهدهندگان اجازه میدهد تا به صورت برنامهنویسی با فرآیند کامپایل تعامل داشته باشند و فراتر از کامپایل ساده کد بروند. شما میتوانید از آن برای موارد زیر استفاده کنید:
- تحلیل کد: بررسی ساختار کد، شناسایی مشکلات احتمالی و استخراج اطلاعات معنایی.
- تبدیل کد: تغییر کد موجود، افزودن ویژگیهای جدید یا ریفکتور کردن کد به صورت خودکار.
- تولید کد: ایجاد کد جدید از ابتدا بر اساس الگوها یا ورودیهای دیگر.
این API برای ساخت ابزارهای توسعه پیچیده که کیفیت کد را بهبود میبخشند، وظایف تکراری را خودکار میکنند و بهرهوری توسعهدهندگان را افزایش میدهند، ضروری است.
درک درخت نحو انتزاعی (AST)
AST یک نمایش درختمانند از ساختار کد شماست. هر گره در این درخت، یک ساختار نحوی مانند تعریف متغیر، فراخوانی تابع یا یک دستور کنترل جریان را نشان میدهد. TypeScript Compiler API ابزارهایی برای پیمایش AST، بازرسی گرههای آن و تغییر آنها فراهم میکند.
این کد ساده TypeScript را در نظر بگیرید:
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));
AST برای این کد، اعلان تابع، دستور بازگشت، template literal، فراخوانی console.log و سایر عناصر کد را نمایش میدهد. تجسم AST میتواند چالشبرانگیز باشد، اما ابزارهایی مانند AST explorer (astexplorer.net) میتوانند کمک کنند. این ابزارها به شما اجازه میدهند کد را وارد کرده و AST مربوط به آن را در قالبی کاربرپسند مشاهده کنید. استفاده از AST Explorer به شما کمک میکند تا نوع ساختار کدی را که دستکاری خواهید کرد، درک کنید.
انواع کلیدی گرههای AST
TypeScript Compiler API انواع مختلفی از گرههای AST را تعریف میکند که هر کدام یک ساختار نحوی متفاوت را نشان میدهند. در اینجا برخی از انواع گرههای رایج آورده شده است:
- SourceFile: نماینده یک فایل کامل TypeScript است.
- FunctionDeclaration: نماینده تعریف یک تابع است.
- VariableDeclaration: نماینده تعریف یک متغیر است.
- Identifier: نماینده یک شناسه (مانند نام متغیر، نام تابع) است.
- StringLiteral: نماینده یک رشتهی متنی است.
- CallExpression: نماینده یک فراخوانی تابع است.
- ReturnStatement: نماینده یک دستور بازگشت است.
هر نوع گره دارای ویژگیهایی است که اطلاعاتی در مورد عنصر کد مربوطه ارائه میدهد. به عنوان مثال، یک گره `FunctionDeclaration` ممکن است ویژگیهایی برای نام، پارامترها، نوع بازگشتی و بدنه خود داشته باشد.
شروع کار با Compiler API
برای شروع استفاده از Compiler API، باید TypeScript را نصب کرده و درک اولیهای از نحو TypeScript داشته باشید. در اینجا یک مثال ساده آورده شده است که نحوه خواندن یک فایل TypeScript و چاپ AST آن را نشان میدهد:
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015, // نسخه هدف ECMAScript
true // SetParentNodes: true برای حفظ ارجاعات والد در AST
);
function printAST(node: ts.Node, indent = 0) {
const indentStr = " ".repeat(indent);
console.log(`${indentStr}${ts.SyntaxKind[node.kind]}`);
node.forEachChild(child => printAST(child, indent + 1));
}
printAST(sourceFile);
توضیحات:
- وارد کردن ماژولها: ماژول `typescript` و ماژول `fs` برای عملیات فایل سیستم وارد میشوند.
- خواندن فایل منبع: محتوای یک فایل TypeScript به نام `example.ts` را میخواند. برای اینکه این کد کار کند، باید یک فایل `example.ts` ایجاد کنید.
- ایجاد SourceFile: یک شیء `SourceFile` ایجاد میکند که ریشه AST را نشان میدهد. تابع `ts.createSourceFile` کد منبع را تجزیه کرده و AST را تولید میکند.
- چاپ AST: یک تابع بازگشتی `printAST` تعریف میکند که AST را پیمایش کرده و نوع هر گره را چاپ میکند.
- فراخوانی printAST: تابع `printAST` را برای شروع چاپ AST از گره ریشه `SourceFile` فراخوانی میکند.
برای اجرای این کد، آن را به عنوان یک فایل `.ts` (مثلاً `ast-example.ts`) ذخیره کنید، یک فایل `example.ts` با مقداری کد TypeScript ایجاد کنید و سپس کد را کامپایل و اجرا کنید:
tsc ast-example.ts
node ast-example.js
این کار AST فایل `example.ts` شما را در کنسول چاپ میکند. خروجی، سلسله مراتب گرهها و انواع آنها را نشان خواهد داد. به عنوان مثال، ممکن است `FunctionDeclaration`، `Identifier`، `Block` و انواع دیگر گرهها را نشان دهد.
پیمایش AST
Compiler API چندین راه برای پیمایش AST فراهم میکند. سادهترین راه استفاده از متد `forEachChild` است، همانطور که در مثال قبلی نشان داده شد. این متد هر گره فرزند از یک گره داده شده را بازدید میکند.
برای سناریوهای پیمایش پیچیدهتر، میتوانید از الگوی `Visitor` استفاده کنید. یک visitor شیئی است که متدهایی را برای فراخوانی برای انواع گرههای خاص تعریف میکند. این به شما امکان میدهد فرآیند پیمایش را سفارشی کرده و بر اساس نوع گره، اقداماتی را انجام دهید.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
class IdentifierVisitor {
visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
console.log(`Found identifier: ${node.text}`);
}
ts.forEachChild(node, n => this.visit(n));
}
}
const visitor = new IdentifierVisitor();
visitor.visit(sourceFile);
توضیحات:
- کلاس IdentifierVisitor: یک کلاس `IdentifierVisitor` با یک متد `visit` تعریف میکند.
- متد Visit: متد `visit` بررسی میکند که آیا گره فعلی یک `Identifier` است یا خیر. اگر باشد، متن شناسه را چاپ میکند. سپس به صورت بازگشتی `ts.forEachChild` را برای بازدید از گرههای فرزند فراخوانی میکند.
- ایجاد Visitor: یک نمونه از `IdentifierVisitor` ایجاد میکند.
- شروع پیمایش: متد `visit` را روی `SourceFile` برای شروع پیمایش فراخوانی میکند.
این مثال نحوه یافتن تمام شناسهها در AST را نشان میدهد. شما میتوانید این الگو را برای یافتن انواع دیگر گرهها و انجام اقدامات مختلف تطبیق دهید.
تبدیل AST
قدرت واقعی Compiler API در توانایی آن برای تبدیل AST نهفته است. شما میتوانید AST را برای تغییر ساختار و رفتار کد خود تغییر دهید. این اساس ابزارهای ریفکتورینگ کد، تولیدکنندگان کد و سایر ابزارهای پیشرفته است.
برای تبدیل AST، باید از تابع `ts.transform` استفاده کنید. این تابع یک `SourceFile` و لیستی از توابع `TransformerFactory` را میگیرد. یک `TransformerFactory` تابعی است که یک `TransformationContext` را میگیرد و یک تابع `Transformer` را برمیگرداند. تابع `Transformer` مسئول بازدید و تبدیل گرهها در AST است.
در اینجا یک مثال ساده آورده شده است که نحوه اضافه کردن یک کامنت به ابتدای یک فایل TypeScript را نشان میدهد:
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
const transformerFactory: ts.TransformerFactory = context => {
return transformer => {
return node => {
if (ts.isSourceFile(node)) {
// Create a leading comment
const comment = ts.addSyntheticLeadingComment(
node,
ts.SyntaxKind.MultiLineCommentTrivia,
" This file was automatically transformed ",
true // hasTrailingNewLine
);
return node;
}
return node;
};
};
};
const { transformed } = ts.transform(sourceFile, [transformerFactory]);
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed
});
const result = printer.printFile(transformed[0]);
fs.writeFileSync("example.transformed.ts", result);
توضیحات:
- TransformerFactory: یک تابع `TransformerFactory` تعریف میکند که یک تابع `Transformer` را برمیگرداند.
- Transformer: تابع `Transformer` بررسی میکند که آیا گره فعلی یک `SourceFile` است یا خیر. اگر باشد، با استفاده از `ts.addSyntheticLeadingComment` یک کامنت پیشرو به گره اضافه میکند.
- ts.transform: تابع `ts.transform` را برای اعمال تبدیل روی `SourceFile` فراخوانی میکند.
- Printer: یک شیء `Printer` برای تولید کد از AST تبدیل شده ایجاد میکند.
- چاپ و نوشتن: کد تبدیل شده را چاپ کرده و آن را در یک فایل جدید به نام `example.transformed.ts` مینویسد.
این مثال یک تبدیل ساده را نشان میدهد، اما شما میتوانید از همین الگو برای انجام تبدیلهای پیچیدهتر مانند ریفکتور کردن کد، اضافه کردن دستورات لاگگیری یا تولید مستندات استفاده کنید.
تکنیکهای پیشرفته تبدیل
در اینجا برخی از تکنیکهای پیشرفته تبدیل که میتوانید با Compiler API استفاده کنید آورده شده است:
- ایجاد گرههای جدید: از توابع `ts.createXXX` برای ایجاد گرههای جدید AST استفاده کنید. به عنوان مثال، `ts.createVariableDeclaration` یک گره تعریف متغیر جدید ایجاد میکند.
- جایگزینی گرهها: گرههای موجود را با گرههای جدید با استفاده از تابع `ts.visitEachChild` جایگزین کنید.
- افزودن گرهها: گرههای جدید را با استفاده از توابع `ts.updateXXX` به AST اضافه کنید. به عنوان مثال، `ts.updateBlock` یک بلوک دستور را با دستورات جدید بهروز میکند.
- حذف گرهها: گرهها را با برگرداندن `undefined` از تابع transformer از AST حذف کنید.
تولید کد
پس از تبدیل AST، باید از آن کد تولید کنید. Compiler API یک شیء `Printer` برای این منظور فراهم میکند. `Printer` یک AST را گرفته و یک نمایش رشتهای از کد را تولید میکند.
تابع `ts.createPrinter` یک شیء `Printer` ایجاد میکند. شما میتوانید پرینتر را با گزینههای مختلفی مانند کاراکتر خط جدید برای استفاده و اینکه آیا کامنتها را منتشر کند یا نه، پیکربندی کنید.
متد `printer.printFile` یک `SourceFile` را گرفته و یک نمایش رشتهای از کد را برمیگرداند. سپس میتوانید این رشته را در یک فایل بنویسید.
کاربردهای عملی Compiler API
TypeScript Compiler API کاربردهای عملی متعددی در توسعه نرمافزار دارد. در اینجا چند نمونه آورده شده است:
- لینترها: ساخت لینترهای سفارشی برای اعمال استانداردهای کدنویسی و شناسایی مشکلات احتمالی در کد شما.
- فرمتدهندههای کد: ایجاد فرمتدهندههای کد برای فرمتبندی خودکار کد شما بر اساس یک راهنمای سبک خاص.
- تحلیلگران استاتیک: توسعه تحلیلگران استاتیک برای شناسایی باگها، آسیبپذیریهای امنیتی و گلوگاههای عملکردی در کد شما.
- تولیدکنندگان کد: تولید کد از الگوها یا ورودیهای دیگر، خودکارسازی وظایف تکراری و کاهش کد تکراری (boilerplate). به عنوان مثال، تولید کلاینتهای API یا اسکیمای پایگاه داده از یک فایل توصیف.
- ابزارهای ریفکتورینگ: ساخت ابزارهای ریفکتورینگ برای تغییر نام خودکار متغیرها، استخراج توابع یا انتقال کد بین فایلها.
- اتوماسیون بینالمللیسازی (i18n): استخراج خودکار رشتههای قابل ترجمه از کد TypeScript شما و تولید فایلهای محلیسازی برای زبانهای مختلف. به عنوان مثال، یک ابزار میتواند کد را برای رشتههایی که به تابع `translate()` ارسال میشوند اسکن کرده و به طور خودکار آنها را به یک فایل منبع ترجمه اضافه کند.
مثال: ساخت یک لینتر ساده
بیایید یک لینتر ساده بسازیم که متغیرهای استفاده نشده را در کد TypeScript بررسی میکند. این لینتر متغیرهایی را که تعریف شدهاند اما هرگز استفاده نشدهاند، شناسایی میکند.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
function findUnusedVariables(sourceFile: ts.SourceFile) {
const usedVariables = new Set();
function visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
usedVariables.add(node.text);
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
const unusedVariables: string[] = [];
function checkVariableDeclaration(node: ts.Node) {
if (ts.isVariableDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
const variableName = node.name.text;
if (!usedVariables.has(variableName)) {
unusedVariables.push(variableName);
}
}
ts.forEachChild(node, checkVariableDeclaration);
}
checkVariableDeclaration(sourceFile);
return unusedVariables;
}
const unusedVariables = findUnusedVariables(sourceFile);
if (unusedVariables.length > 0) {
console.log("Unused variables:");
unusedVariables.forEach(variable => console.log(`- ${variable}`));
} else {
console.log("No unused variables found.");
}
توضیحات:
- تابع findUnusedVariables: یک تابع `findUnusedVariables` تعریف میکند که یک `SourceFile` را به عنوان ورودی میگیرد.
- مجموعه usedVariables: یک `Set` برای ذخیره نام متغیرهای استفاده شده ایجاد میکند.
- تابع visit: یک تابع بازگشتی `visit` تعریف میکند که AST را پیمایش کرده و نام تمام شناسهها را به مجموعه `usedVariables` اضافه میکند.
- تابع checkVariableDeclaration: یک تابع بازگشتی `checkVariableDeclaration` تعریف میکند که بررسی میکند آیا یک تعریف متغیر استفاده نشده است. اگر چنین باشد، نام متغیر را به آرایه `unusedVariables` اضافه میکند.
- بازگرداندن unusedVariables: آرایهای حاوی نام متغیرهای استفاده نشده را برمیگرداند.
- خروجی: متغیرهای استفاده نشده را در کنسول چاپ میکند.
این مثال یک لینتر ساده را نشان میدهد. شما میتوانید آن را برای بررسی استانداردهای دیگر کدنویسی و شناسایی سایر مشکلات احتمالی در کد خود گسترش دهید. به عنوان مثال، میتوانید واردات استفاده نشده، توابع بیش از حد پیچیده یا آسیبپذیریهای امنیتی بالقوه را بررسی کنید. نکته کلیدی این است که بدانید چگونه AST را پیمایش کرده و انواع گرههای خاص مورد علاقه خود را شناسایی کنید.
بهترین شیوهها و ملاحظات
- AST را درک کنید: برای درک ساختار AST وقت بگذارید. از ابزارهایی مانند AST explorer برای تجسم AST کد خود استفاده کنید.
- از Type Guards استفاده کنید: از type guards (`ts.isXXX`) برای اطمینان از اینکه با انواع گرههای صحیح کار میکنید، استفاده کنید.
- عملکرد را در نظر بگیرید: تبدیلهای AST میتوانند از نظر محاسباتی سنگین باشند. کد خود را برای به حداقل رساندن تعداد گرههایی که بازدید و تبدیل میکنید، بهینه کنید.
- خطاها را مدیریت کنید: خطاها را به خوبی مدیریت کنید. Compiler API ممکن است در صورت تلاش برای انجام عملیات نامعتبر روی AST، استثنا پرتاب کند.
- به طور کامل تست کنید: تبدیلهای خود را به طور کامل تست کنید تا اطمینان حاصل کنید که نتایج مورد نظر را تولید میکنند و باگهای جدیدی ایجاد نمیکنند.
- از کتابخانههای موجود استفاده کنید: استفاده از کتابخانههای موجود را که انتزاعات سطح بالاتری را بر روی Compiler API ارائه میدهند، در نظر بگیرید. این کتابخانهها میتوانند وظایف رایج را ساده کرده و میزان کدی را که باید بنویسید کاهش دهند. نمونهها شامل `ts-morph` و `typescript-eslint` هستند.
نتیجهگیری
TypeScript Compiler API یک ابزار قدرتمند برای ساخت ابزارهای توسعه پیشرفته است. با درک نحوه کار با AST، میتوانید لینترها، فرمتدهندههای کد، تحلیلگران استاتیک و ابزارهای دیگری ایجاد کنید که کیفیت کد را بهبود میبخشند، وظایف تکراری را خودکار میکنند و بهرهوری توسعهدهندگان را افزایش میدهند. اگرچه این API میتواند پیچیده باشد، اما مزایای تسلط بر آن قابل توجه است. این راهنمای جامع، پایهای برای کاوش و استفاده موثر از Compiler API در پروژههای شما فراهم میکند. به یاد داشته باشید که از ابزارهایی مانند AST Explorer استفاده کنید، انواع گرهها را با دقت مدیریت کنید و تبدیلهای خود را به طور کامل تست کنید. با تمرین و پشتکار، میتوانید پتانسیل کامل TypeScript Compiler API را باز کرده و راهحلهای نوآورانهای برای چشمانداز توسعه نرمافزار بسازید.
برای مطالعه بیشتر:
- مستندات TypeScript Compiler API: [https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
- AST Explorer: [https://astexplorer.net/](https://astexplorer.net/)
- کتابخانه ts-morph: [https://ts-morph.com/](https://ts-morph.com/)
- typescript-eslint: [https://typescript-eslint.io/](https://typescript-eslint.io/)